#include <stddef.h>
#include <string.h>
#include <uuid/uuid.h>
#include <stdio.h>
#include <stdlib.h>

#define NIT_SHORT_NAMES
#include <nit/macros.h>
#include <nit/palloc.h>
#include <nit/list.h>
#include <nit/gap-buf.h>

#include "des.h"
#include "cell.h"
#include "sym.h"
#include "rel.h"
#include "pur.h"
#include "frm.h"
#include "fld.h"
#include "prd.h"
#include "ser.h"

enum den_type { NIL = 0, DEN_ID, LS, LE, DEN_FLD };

static int
ser_nil(Nit_gap *gap)
{
	return gap_write(gap, &(char){ NIL }, 1);
}

static int
ser_id(Nit_gap *gap, uuid_t id)
{
	return gap_write(gap, id, sizeof(uuid_t));
}

static int
ser_den_id(Nit_gap *gap, uuid_t id)
{
	return gap_write(gap, &(char){ DEN_ID }, 1) && ser_id(gap, id);
}

static int
ser_den_ls(Nit_gap *gap)
{
	return gap_write(gap, &(char){ LS }, 1);
}

static int
ser_den_le(Nit_gap *gap)
{
	return gap_write(gap, &(char){ LE }, 1);
}

static int
ser_fld(Nit_gap *gap, Inb_fld *fld);

static int
ser_den_fld(Nit_gap *gap, Inb_fld *fld)
{
	return gap_write(gap, &(char){ DEN_FLD }, 1) &&
		ser_fld(gap, fld);
}

static int
ser_cell(Nit_gap *gap, Inb_cell *cell)
{
	if (!ser_den_ls(gap))
		return 0;

	foreach (cell) {
		if (!cell->here) {
			if (!ser_nil(gap))
				return 0;

			continue;
		}

		switch (inb_type(cell->here)) {
		case CELL:
			if (!ser_cell(gap, cell->here))
				return 0;
			break;
		case FLD:
			if (!ser_den_fld(gap, cell->here))
				return 0;

			break;
		default:
			if (!(ser_den_id(gap, cell->here)))
				return 0;
		}
	}

        return ser_den_le(gap);
}

static int
ser_sym(Nit_gap *gap, Inb_sym *sym)
{
	size_t size = strlen(sym->str) + 1;

	return ser_id(gap, sym->id) &&
		gap_write(gap, &size, sizeof(size)) &&
		gap_write(gap, sym->str, size);
}

static int
ser_rel(Nit_gap *gap, Inb_rel *rel)
{
	if (!ser_id(gap, rel->id))
		return 0;

	if (!rel->sub)
		return ser_nil(gap);

	return ser_cell(gap, rel->sub);
}

static int
ser_pur(Nit_gap *gap, Inb_pur *pur)
{
	if (!ser_id(gap, pur->id))
		return 0;

	if (!pur->sub)
		return ser_nil(gap);

	return ser_cell(gap, pur->sub);
}

static int
ser_frm(Nit_gap *gap, Inb_frm *frm)
{
	if (!ser_id(gap, frm->id))
		return 0;

	if (!frm->flds)
		return ser_nil(gap);

	return ser_cell(gap, frm->flds);
}

static int
ser_fld(Nit_gap *gap, Inb_fld *fld)
{
	return ser_id(gap, fld->id) &&
		gap_write(gap, fld->frm->id, sizeof(fld->frm->id)) &&
		gap_write(gap, fld->pur->id, sizeof(fld->pur->id));
}

static int
ser_prd(Nit_gap *gap, Inb_prd *prd)
{
        if (!(ser_id(gap, prd->id) &&
	      ser_den_fld(gap, prd->out) &&
	      ser_cell(gap, prd->args)))
		return 0;

	if (inb_des(prd)->flags & INB_PRDF)
		return ser_nil(gap);

	return ser_cell(gap, prd->d.bod);
}

int
inb_ser(Nit_gap *gap, void *dat)
{
	Inb_des *des;

	if (!dat)
		return ser_nil(gap);

	des = inb_des(dat);
	gap_write(gap, inb_des(dat), sizeof(*des));

	switch (inb_type(dat)) {
	case CELL:
		return 0; /* You shouldn't be serializing this! */
	case SYM:
		return ser_sym(gap, dat);
	case REL:
		return ser_rel(gap, dat);
	case PUR:
		return ser_pur(gap, dat);
	case FRM:
		return ser_frm(gap, dat);
	case FLD:
		return 0; /* What's your problem?! */
	case PRD:
		return ser_prd(gap, dat);
	}

	return 0; /* YOU are the problem */
}

/* static void */
/* inb_derser_switch() */
/* { */

/* } */

Inb_sym *
deser_sym(Nit_gap *gap)
{
	Inb_sym *sym;
	Inb_des *des = inb_palloc(sym);
	char *str;
	size_t str_size;
	size_t pos = sizeof(*des);

	pcheck(des, NULL);
	memcpy(des, gap_ptr(gap, 0), pos);
	sym = inb_thing(des);
	uuid_copy(sym->id, gap_ptr(gap, pos));
	str_size = *(size_t *) gap_ptr(gap, pos += sizeof(uuid_t));
	pcheck_c(str = malloc(str_size), NULL, free(des));
	memcpy(str, gap_ptr(gap, pos += sizeof(size_t)), str_size);
        str[str_size] = '\0';
	sym->str = str;
	return sym;
}

void *
inb_deser(Nit_gap *gap)
{
	/* Do work */
	return deser_sym(gap);
}

enum print_state { STATE_DES_TYPE, STATE_DES_FLAGS, STATE_TOP_ID, STATE_SCAN,
		   STATE_ID, STATE_FLD_ID, STATE_FLD_FRM, STATE_FLD_PUR };

static void
inb_ser_print_switch(enum print_state *state, ptrdiff_t *i, char *val)
{
	char den;

	switch (*state) {
	case STATE_DES_TYPE:
		switch (*(enum inb_type *) val) {
		case CELL:
			printf("cell");
			break;
		case SYM:
			printf("sym");
			break;
		case REL:
			printf("rel");
			break;
		case PUR:
			printf("pur");
			break;
		case FRM:
			printf("frm");
			break;
		case FLD:
			printf("fld");
			break;
		case PRD:
			printf("prd");
			break;
		}

		*state = STATE_DES_FLAGS;
		*i += sizeof(enum inb_type);
		break;
	case STATE_DES_FLAGS:
		printf(" flags");
		*state = STATE_TOP_ID;
		*i += sizeof(unsigned int);
		break;
	case STATE_TOP_ID:
		printf(" top_id");
		*state = STATE_SCAN;
		*i += sizeof(uuid_t);
		break;
	case STATE_SCAN:
		den = *val;

		switch ((enum den_type) den) {
		case NIL:
			printf(" nil");
			break;
		case DEN_ID:
			printf(" id");
			*state = STATE_ID;
			break;
		case LS:
			printf(" (");
			break;
		case LE:
			printf(" )");
			break;
		case DEN_FLD:
			printf(" [");
			*state = STATE_FLD_ID;
			break;
		}

		*i += 1;
		break;
	case STATE_ID:
		printf(" _id");
		*state = STATE_SCAN;
		*i += sizeof(uuid_t);
		break;
	case STATE_FLD_ID:
		printf(" id");
		*state = STATE_FLD_FRM;
		*i += sizeof(uuid_t);
		break;
	case STATE_FLD_FRM:
		printf(" frm_id");
		*state = STATE_FLD_PUR;
		*i += sizeof(uuid_t);
		break;
	case STATE_FLD_PUR:
		printf(" pur_id]");
		*state = STATE_SCAN;
		*i += sizeof(uuid_t);
		break;
	}
}

void
inb_ser_print(Nit_gap *gap)
{
        char *val = gap->bytes;
	enum print_state state = STATE_DES_TYPE;
	ptrdiff_t i;

	for (i = 0; i < gap->start; val = gap->bytes + i)
		inb_ser_print_switch(&state, &i, val);

	for (i = gap->end + 1; i < (ptrdiff_t) gap->size - 1; val = gap->bytes + i)
		inb_ser_print_switch(&state, &i, val);
}
